package dbPicasso::UI;

use Data::Dumper;
use dbPicasso::Database;
use dbPicasso::UI::Dialog::dlgGetObjectSpec;
use dbPicasso::UI::Dialog::dlgConnectionManager;
use dbPicasso::UI::Dialog::dlgAbout;
use dbPicasso::UI::Dialog::dlgFind;
use dbPicasso::Config;
use dbPicasso::PluginRegistry;

use IO::AtomicFile;

#use Gtk2::Ex::Simple::List;
use Gtk2::Ex::Dialogs::ChooseFile (
                                   destroy_with_parent => TRUE,
                                   modal               => TRUE
                                  );

#GLOBALS

=head1

TODO: The database connection will need to be kept in a hash associated
with its connection window. Right now it's a global because we only have
a single connection window, but when we try to support multiple connections,
it's going to be a bit of an issue. 

$CODE also should be part of the instance object once we create it. I'm actually 
thinking that the dbPicasso::Database instance can hold references to 
all of its associated GUI parts (like the source view, notebook page, and result window).

=cut

our $UI;

# These globals need to die.
our $DB_CONN;
our $CODE;
our $CURRENT_FILE;
our $CONFIG;

#Initialize configuration
#dbPicasso::Config->load(); # loads config at module init now.

#-------------------#
#   Public methods  #
#-------------------#

sub init
{

    # Fire on_app_start event
    dbPicasso::PluginRegistry->fire_event('on_app_start', $UI);
}

=head1 set_UI_Source

Class method to set the $UI glade object.

=cut

sub set_UI_Source
{
    my $pkg = shift;
    $UI = shift;

    return 1;
}

sub set_code_entry
{
    my $class = shift;
    $CODE = shift;
}

sub status
{
    my $msg = shift;

    print scalar localtime(time) . " $msg\n" if $msg;

    my $appbar = $UI->get_widget("statusbar");

    if ($appbar)
    {
        $appbar->set_status($msg);
    }

    return 1;
}

sub message
{
    my $msg = shift;
    chomp $msg;

    my $messagewin = $UI->get_widget("Messages")->get_buffer;

    if ($messagewin)
    {
        $messagewin->insert($messagewin->get_end_iter(), scalar localtime(time) . " $msg\n");
    }

    return 1;
}

=head1 addSourceView

This sub is a hack, because I can't figure out how I'm supposed to do it in Glade. 
GtkSourceView widgets are not available in the Glade pallette, and I seem to be missing
something when it comes to the "custom widget" functionality.

=cut

sub AddSourceView
{
    my $class     = shift;
    my $gladexml  = shift || $UI;
    my $container = shift || "codeVBox";

    my $lm = Gtk2::SourceView::LanguagesManager->new;

    my $lang = $lm->get_language_from_mime_type("text/x-sql");

    my $sb;

    $sb = Gtk2::SourceView::Buffer->new_with_language($lang);
    $sb->set_highlight(1);

    my $codebox = $gladexml->get_widget($container);

    my $sw = Gtk2::ScrolledWindow->new;
    $sw->set_policy('automatic', 'automatic');
    $sw->show;

    $codebox->add($sw);

    my $view = Gtk2::SourceView::View->new_with_buffer($sb);

    # These should be options.

    $view->set_show_line_numbers(1);
    $view->set_show_line_markers(1);
    $view->set_show_margin(0);
    $view->set_auto_indent(1);
    $view->set_smart_home_end(1);
    $view->set_insert_spaces_instead_of_tabs(1);

    # Add default_font tag for a non-proportional font.
    $sb->apply_tag($sb->create_tag('source_font', 'font' => 'Monospace 10'), $sb->get_bounds);

    $sb->signal_connect('changed' => \&dbPicasso::UI::_apply_text_tags);

    $sw->add($view);
    $view->show;

    return $view;
}

sub fill_simple_comboboxentry
{
    my $class   = shift;
    my $cbe     = shift;
    my $listref = shift;
    my $textcol = shift || 0;    # which column to use as the value list

    my $numcols = ref($$listref[0]) ? scalar @{$$listref[0]} : 1;
    my $list    = Gtk2::ListStore->new(('Glib::String') x $numcols);

    map {
        my $item = $_;

        if (ref($_))
        {
            my %cols = (map { $_ => $item->[$_] } (0 .. $numcols-1));
            $list->set($list->append, %cols);
        }
        else
        {
            $list->set($list->append, (0 => $item));
        }
    } @{$listref};

    $cbe->set_model($list);
    $cbe->set_text_column($textcol);
}

sub FillConnectionList
{
    my $cxname = $UI->get_widget("ConnectionName");
    my $cxlist = dbPicasso::Config->get_section("Connections");
    my @names  = sort keys %{$cxlist};

    dbPicasso::UI->fill_simple_comboboxentry($cxname, \@names);
}

sub GetUI
{
    $UI;
}

#-------------------#
#  Private methods  #
#-------------------#

sub _apply_text_tags
{
    my $buffer = shift;

    # I think this is not the best way to do it. This is being called _EVERY TIME_
    # the user presses a key in the GtkSourceView. There's got to be a better way.
    $buffer->apply_tag_by_name('source_font', $buffer->get_bounds);
}

sub _set_button_sensitivities
{
    my $sensitive = shift;

    my @buttons = qw( ExecuteQuery
      QueryBook
      tbLoadObject
      );

    map {
        my $btn = $UI->get_widget($_);
        $btn->set_sensitive($sensitive);
    } @buttons;

    status undef;

    return 1;
}

sub _search_equal
{
    return ($_[0] eq $_[1]) ? 1 : 0;
}

sub _add_result_grid
{
    my $vbox      = shift;
    my $resultset = shift;

    my $resGrid = Gtk2::TreeView->new();

    # $widget, $expand, $fill, $padding
    $vbox->pack_start($resGrid, 1, 1, 3);

    if (scalar @{$resultset->{NAME}} or $resultset eq 'MESSAGE')
    {

        # Renderer for most columns
        my $regRenderer = Gtk2::CellRendererText->new;
        $regRenderer->set("font",     "Bitstream Vera Sans Mono 10");
        $regRenderer->set("editable", 1);

        # Renderer for the rowcount column.
        my $rcRenderer = Gtk2::CellRendererText->new;
        $rcRenderer->set("font",     "Bitstream Vera Sans Mono 10");
        $rcRenderer->set("editable", 0);

        # Always create at least one column for rowcount
        # or system messages. That's the special '__sys__' column.

        my $colnum = 0;
        map {
            my $header = $_ eq '__sys__' ? '' : $_;
            my $renderer =
                $_ eq '__sys__'
              ? $rcRenderer
              : $regRenderer;

            $header =~ s/_/__/g;    # we want literal underscores, not accelerators

            $resGrid->append_column(
                            Gtk2::TreeViewColumn->new_with_attributes($header, $renderer, text => $colnum++));

        } ('__sys__', @{$resultset->{NAME}});
    }

    $resGrid->set_rules_hint(1);

    $resGrid->show;

    return $resGrid;
}

sub _show_results
{
    my $resultset = shift;
    my $box       = shift || "ResultsVBox";

    my $displayResults;

    do
    {
        if (scalar @{$resultset->{NAME}})
        {
            $displayResults = 1;
            my $numcols = scalar(@{$resultset->{NAME}});

            my $resgrid = &_add_result_grid($UI->get_widget($box), $resultset);
            my $store = Gtk2::ListStore->new(('Glib::String') x ($numcols+1));

            my $rowcount = 0;
            while ($resultset and (my $row = $resultset->fetchrow_arrayref))
            {

                # I wish there were a way to populate this without position-shifting all of the elements.

                $store->set_value(
                                  $store->append,
                                  (
                                   0 => ++$rowcount,
                                   map { ($_ => $$row[$_-1]) } 1 .. ($numcols)
                                  )
                                 );
            }

            $resgrid->set_model($store);
            $resgrid->columns_autosize;

        }
        else
        {
            my $resgrid = &_add_result_grid($UI->get_widget("ResultsVBox"), 'MESSAGE');
            my $store   = Gtk2::ListStore->new('Glib::String');

            $store->set_value($store->append, 0 => "Query completed without any errors");

            $resgrid->set_model($store);
            $resgrid->columns_autosize;
        }

    } while ($resultset->more_results);

    $resultset->finish;
    status "Query completed without any errors";

    return $displayResults ? 1 : 0;

}

sub _clear_result_grid
{
    my $box    = shift || "ResultsVBox";
    my $resbox = $UI->get_widget($box);

    foreach my $child ($resbox->get_children)
    {
        $resbox->remove($child);
    }

    status undef;

    return 1;
}

sub _clear_messages
{
    $UI->get_widget("Messages")->get_buffer->set_text(undef);
}

sub _save_file
{
    my $filename = shift;

    unless ($filename)
    {
        $filename = ask_to_save Gtk2::Ex::Dialogs::ChooseFile($ENV{HOME});
    }

    return undef unless $filename;

    status "Saving $filename...";

    my $buffer = $CODE->get_buffer;

    my $OUTFILE = IO::AtomicFile->open($filename, 'w');

    unless ($OUTFILE)
    {
        status("Couldn't open file '$filename'");
        return undef;
    }

    print $OUTFILE $buffer->get_text($buffer->get_start_iter, $buffer->get_end_iter, 0);

    $OUTFILE->close;

    $buffer->set_modified(0);

    &_set_current_file($filename);    # For Save-As or 1st-time save
    status("Saved $filename");

    return 1;
}

sub _open_file
{
    my $filename = ask_to_open Gtk2::Ex::Dialogs::ChooseFile($ENV{HOME});

    # The following is mostly swiped from the SourceView perldoc.

    return undef unless $filename;

    unless (open INFILE, "<$filename")
    {
        status("Unable to open $filename");
        message("Unable to open $filename: $!");    # more detailed error

        return undef;
    }

    my $buffer = $CODE->get_buffer;

    # Do this AFTER we make sure we can open the file.
    $buffer->begin_not_undoable_action();
    $buffer->set_text(undef);

    while (<INFILE>)
    {
        $buffer->insert($buffer->get_end_iter(), $_);
    }

    close INFILE;

    $buffer->end_not_undoable_action();

    $buffer->set_modified(0);
    $buffer->place_cursor($buffer->get_start_iter());

    $buffer->apply_tag_by_name('source_font', $buffer->get_bounds);

    &_set_current_file($filename);
    status "Opened $filename";

    return 1;
}

sub _error_handler
{
    my ($err, $sev, $state, $line, $server, $proc, $msg, $sql, $err_type) = @_;

    message join(" ", $msg);
}

sub _set_current_file
{
    my $filename = shift;

    $CURRENT_FILE = $filename;

    $filename ||= "No open file";

    my $window = $UI->get_widget("dbPicasso");
    $window->set_title("dbPicasso - $filename");

    return 1;
}

#-------------------------#
#   GUI Callback methods  #
#-------------------------#

sub on_dbPicasso_delete_event
{

    &_appExit;
}

sub on_file_quit_activate
{
    &_appExit;
}

sub _appExit
{
    dbPicasso::PluginRegistry->fire_event('on_app_shutdown', $UI);
    Gtk2->main_quit;
}

sub on_Connect_toggled
{
    my $connect    = shift;
    my $connStatus = undef;

    if ($connect->get_active)    # Connect
    {
        my $cxname = $UI->get_widget("ConnectionName")->child->get_text;
        my $cxlist = dbPicasso::Config->get_section("Connections");

        my $cxinfo = $$cxlist{$cxname};

        dbPicasso::PluginRegistry->fire_event('before_connect', $UI, $cxinfo);

        $DB_CONN = dbPicasso::Database->connect(%{$cxinfo});

        $connStatus = $DB_CONN ? 1 : 0;
        status $connStatus
          ? "Connected to database '$cxname'"
          : "Unable to connect to database '$cxname'!";

        if ($connStatus)
        {
            &_set_button_sensitivities(1);
            $DB_CONN->set_error_handler(\&_error_handler);

            dbPicasso::PluginRegistry->fire_event('after_connect', $UI, $DB_CONN);
        }
        else
        {
            $connect->set_active(0);
            dbPicasso::PluginRegistry->fire_event('on_connect_failed', $UI, $cxinfo);
        }
    }
    else    # Disconnect
    {
        if ($DB_CONN)
        {
            dbPicasso::PluginRegistry->fire_event('before_disconnect', $UI);

            $DB_CONN->disconnect;
            $DB_CONN = undef;

            &_set_button_sensitivities(0);
            &_clear_result_grid;

            dbPicasso::PluginRegistry->fire_event('after_disconnect', $UI);

            status "Disconnected.";
        }
    }

    return 1;
}

sub on_ExecuteQuery_clicked
{
    my $execute = shift;

    my $buffer = $CODE->get_buffer;

    my ($sstart, $send) = $buffer->get_selection_bounds;

    $sstart ||= $buffer->get_start_iter;
    $send   ||= $buffer->get_end_iter;

    my $query = $buffer->get_text($sstart, $send, 0);

    &_clear_result_grid;
    &_clear_messages;

    my $batches = $DB_CONN->splitQueryBatch($query);

    my $qrynum;
    foreach $batch (@{$batches})
    {
        $qrynum++;

        dbPicasso::PluginRegistry->fire_event('before_query', $UI, $batch);

        my $result = $DB_CONN->execQuery($batch);

        dbPicasso::PluginRegistry->fire_event('after_query', $UI, $batch, $result);

        if ($result)
        {
            if (&_show_results($result))
            {
                my $querybook = $UI->get_widget("QueryBook");
                $querybook->set_current_page(1);
            }
        }
        else
        {
            message "ERROR: " . $DB_CONN->errstr;
            return undef;    # stop on error
        }
    }

    status("Query completed without errors.");

    return 1;
}

sub on_ToolOpen_clicked
{
    return &_open_file;
}

sub on_file_open_activate
{
    return &_open_file;
}

sub on_ToolSave_clicked
{
    return &_save_file($CURRENT_FILE);
}

sub on_file_save_activate
{
    return &_save_file($CURRENT_FILE);
}

sub on_file_saveAs_activate
{
    return &_save_file(undef);
}

sub on_ToolNew_clicked
{
    my $buffer = $CODE->get_buffer;

    $buffer->begin_not_undoable_action();
    $buffer->set_text(undef);
    $buffer->end_not_undoable_action();

    $buffer->set_modified(0);

    &_set_current_file(undef);

    return 1;
}

sub on_edit_object_def_activate
{
    dbPicasso::UI::Dialog::dlgGetObjectSpec->show();
}

sub on_connectionmanager_activate
{
    dbPicasso::UI::Dialog::dlgConnectionManager->show();
    &FillConnectionList;    # refresh the connection select box on the main GUI
}

sub on_about_activate
{
    dbPicasso::UI::Dialog::dlgAbout->show();
}

## Edit menu callbacks

sub on_EditCopy_activate
{
    $CODE->get_buffer->copy_clipboard($CODE->get_clipboard);
}

sub on_EditCut_activate
{
    $CODE->get_buffer->cut_clipboard($CODE->get_clipboard, 1);
}

sub on_EditPaste_activate
{
    $CODE->get_buffer->paste_clipboard($CODE->get_clipboard, undef, 1);
}

sub on_EditFind_activate
{
	dbPicasso::UI::Dialog::dlgFind->show();
}

# Query Menu

sub on_execute_to_file_activate
{
	
}

sub AUTOLOAD
{
    print "Uncaught signal detected.\n";
    return 1;
}

1;
